package org.termine.logic;

import java.io.Serializable;
import java.sql.Connection;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import javax.faces.bean.ApplicationScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;

import org.slf4j.LoggerFactory;
import org.termine.dao.AvailabilityDAO;
import org.termine.entity.Availability;

@ManagedBean
@ApplicationScoped
public class AvailabilityManager extends BaseManager implements Serializable {

	private static final long serialVersionUID = 5183274745600716878L;

	private static final org.slf4j.Logger logger = LoggerFactory
			.getLogger(AvailabilityManager.class);

	@ManagedProperty(value = "#{availabilityDAO}")
	private AvailabilityDAO availabilityDAO;

	public String addAvailability(Availability availability, Integer providerId) {
		if (availability.getStart().before(availability.getEnd())
				&& !availability.getStart().equals(availability.getEnd())
				&& availability.getStart().after(new Date())) {

			Connection connection = null;
			try {
				connection = this.connector.getConnection();

				if (availability.getType().equals(Availability.AVAILABILITY)) {
					if (this.getAvailabilityDAO().overlapsWithExistingAvailabilities(connection,
							availability, providerId)) {
						return "OVERLAPS_WITH_AVAILABILITY";
					} else {
						this.getAvailabilityDAO().addAvailability(connection, availability, providerId);
						this.connector.commit(connection);
						return "SUCCESS";
					}
				} else if (availability.getType().equals(Availability.UNAVAILABILITY)) {
					this.getAvailabilityDAO().addAvailability(connection, availability, providerId);

					availability.setAvailability_id(this.getAvailabilityDAO().getAvailabilityId(
							connection, availability, providerId));

					if (this.getAvailabilityDAO().getNumberOfCorrespondingAppointments(connection,
							availability) != 0) {
						this.connector.rollback(connection);
						return "OVERLAPS_WITH_APPOINTMENT";
					} else {
						connection.commit();
						this.connector.closeConnection(connection);
						return "SUCCESS";
					}
				} else {
					throw new Exception("Availability type " + availability.getType()
							+ " is not valid!");
				}

			} catch (Exception e) {
				this.connector.rollback(connection);
				logger.error("ERROR: ", e);
				return "ERROR";
			}
		} else {
			return "INVALID";
		}
	}

	public String updateAvailability(Availability availability, Integer providerId) {
		if (availability.getStart().before(availability.getEnd())
				&& !availability.getStart().equals(availability.getEnd())) {

			Connection connection = null;
			try {
				connection = this.connector.getConnection();

				if (availability.getType().equals(Availability.AVAILABILITY)) {
					if (this.getAvailabilityDAO().overlapsWithExistingAvailabilities(connection,
							availability, providerId)) {
						return "OVERLAPS_WITH_AVAILABILITY";
					}

					Integer noCorrespondingAppointments = this.getAvailabilityDAO()
							.getNumberOfCorrespondingAppointments(connection, availability);

					this.getAvailabilityDAO().updateAvailability(connection, availability);

					if (noCorrespondingAppointments == this.getAvailabilityDAO()
							.getNumberOfCorrespondingAppointments(connection, availability)) {
						connection.commit();
						return "SUCCESS";
					} else {
						connection.rollback();
						return "APPOINTMENT_LOST";
					}
				} else if (availability.getType().equals(Availability.UNAVAILABILITY)) {
					if (this.getAvailabilityDAO().getNumberOfCorrespondingAppointments(connection,
							availability) != 0) {
						return "OVERLAPS_WITH_APPOINTMENT";
					} else {
						this.getAvailabilityDAO().updateAvailability(connection, availability);
						connection.commit();
						return "SUCCESS";
					}
				} else {
					throw new Exception("Availability type " + availability.getType()
							+ " is not valid!");
				}
			} catch (Exception e) {
				logger.error("ERROR: ", e);
				return "ERROR";
			}
		} else {
			return "INVALID";
		}
	}

	public String deleteAvailability(Availability availability) {
		Connection connection = null;
		try {
			if (availability.getType().equals(Availability.AVAILABILITY)) {
				connection = this.connector.getConnection();
				if (this.getAvailabilityDAO().getNumberOfCorrespondingAppointments(connection,
						availability).equals(new Integer(0))) {
					this.getAvailabilityDAO().deleteAvailability(connection, availability);
					this.connector.commit(connection);
					return "SUCCESS";
				} else {
					this.connector.closeConnection(connection);
					return "APPOINTMENT_LOST";
				}
			} else if (availability.getType().equals(Availability.UNAVAILABILITY)) {
				connection = this.connector.getConnection();
				this.getAvailabilityDAO().deleteAvailability(connection, availability);
				this.connector.commit(connection);
				return "SUCCESS";
			} else {
				throw new Exception("Availability type " + availability.getType()
						+ " is not valid!");
			}
		} catch (Exception e) {
			this.connector.rollback(connection);
			logger.error("ERROR: ", e);
			return "ERROR";
		}
	}

	public List<Availability> getAvailabilitiesForProvider(Integer providerId) {
		Connection connection = null;
		try {
			connection = this.connector.getConnection();
			return this.getAvailabilityDAO().getAvailabilitiesForProvider(connection, providerId);
		} catch (Exception e) {
			logger.error("ERROR: ", e);
			return null;
		} finally {
			this.connector.closeConnection(connection);
		}
	}

	public List<Availability> getActualAvailabilitiesForProvider(Integer providerId) {
		Connection connection = null;
		try {
			connection = this.connector.getConnection();
			List<Availability> availabilities = this.getAvailabilityDAO()
					.getAllCurrentAvailabilitiesForProvider(connection, providerId,
							Availability.AVAILABILITY);
			List<Availability> unavailabilities = this.getAvailabilityDAO()
					.getAllCurrentAvailabilitiesForProvider(connection, providerId,
							Availability.UNAVAILABILITY);
			List<Availability> result = new LinkedList<Availability>();

			if (unavailabilities.size() == 0) {
				return availabilities;
			} else {
				for (int i = 0; i < availabilities.size(); i++) {
					for (int k = 0; k < unavailabilities.size(); k++) {
						// if the availability does not overlap with the
						// unavailability
						if (availabilities.get(i).getStart()
								.after(unavailabilities.get(k).getEnd())
								|| availabilities.get(i).getEnd()
										.before(unavailabilities.get(k).getStart())) {
							// add the availability to the result list
							result.add(availabilities.get(i));
						}
						// if the availability overlaps with the unavailability
						else {
							if (unavailabilities.get(k).getStart()
									.before(availabilities.get(i).getStart())
									&& unavailabilities.get(k).getEnd()
											.after(availabilities.get(i).getEnd())) {
								continue;
							} else if (unavailabilities.get(k).getStart()
									.after(availabilities.get(i).getStart())
									&& unavailabilities.get(k).getEnd()
											.before(availabilities.get(i).getEnd())) {
								Availability beforeAvailability = availabilities.get(i).copy();
								beforeAvailability.setEnd(unavailabilities.get(k).getStart());
								result.add(beforeAvailability);
								Availability afterAvailability = availabilities.get(i).copy();
								afterAvailability.setStart(unavailabilities.get(k).getEnd());
								result.add(afterAvailability);
							} else if (unavailabilities.get(k).getStart()
									.before(availabilities.get(i).getStart())
									&& unavailabilities.get(k).getEnd()
											.before(availabilities.get(i).getEnd())) {
								availabilities.get(i).setStart(unavailabilities.get(k).getEnd());
								result.add(availabilities.get(i));
							} else if (unavailabilities.get(k).getStart()
									.after(availabilities.get(i).getStart())
									&& unavailabilities.get(k).getEnd()
											.after(unavailabilities.get(i).getEnd())) {
								availabilities.get(i).setEnd(unavailabilities.get(k).getStart());
								result.add(availabilities.get(i));
							}
						}
					}
				}
				return result;
			}
		} catch (Exception e) {
			logger.error("ERROR: ", e);
			return null;
		} finally {
			this.connector.closeConnection(connection);
		}
	}

	/*
	 * Getters & Setters
	 */
	public AvailabilityDAO getAvailabilityDAO() {
		return this.availabilityDAO;
	}

	public void setAvailabilityDAO(AvailabilityDAO availabilityDAO) {
		this.availabilityDAO = availabilityDAO;
	}

}
